package fons.navigator.ui.browsers.dimensions;

import java.awt.geom.Point2D;
import java.util.Map;
import java.util.Set;

import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.BodyType;
import org.jbox2d.dynamics.World;

import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import edu.umd.cs.piccolo.activities.PActivity;
import edu.umd.cs.piccolo.event.PBasicInputEventHandler;
import edu.umd.cs.piccolo.event.PInputEvent;
import fons.model.ontology.Context;
import fons.model.ontology.Dimension;
import fons.model.ontology.interfaces.ContextDependent;
import fons.navigator.ui.browsers.AbstractBrowser;
import fons.navigator.util.piccolo.ContextMenu;
import fons.navigator.util.piccolo.ContextMenuOwner;
import fons.navigator.util.piccolo.PNodes;
import fons.navigator.views.NavigatorViewCreator;
import fons.navigator.views.ViewRequirements;

public class DimensionBrowserCanvas extends AbstractBrowser implements ContextDependent {
	private static final long serialVersionUID = 1L;
	
	private final Context context;
	private final World world = new World(new Vec2(0, 0));
	private final Set<Block> blocks = Sets.newHashSet();
	private final Set<Connection> connections = Sets.newHashSet();
	
	private final ContextMenu menu;	
	
	private final Map<Dimension, VisualDimension> shownDimensions = Maps.newHashMap();
	
	public DimensionBrowserCanvas (Context ctx) {
		this.context = ctx;
		
		int y = 0;
		for (Dimension top: context.getTopLevelDimensions()) {
			y += showDimension(top, 300, y).getFullBounds().getHeight() + 10;
		}
		
		updatePositions();
		this.getLayer().addActivity(new PActivity(-1, 20) {
			@Override
			protected void activityStep(long elapsedTime) {
				world.step(1.0f/30.0f, 16, 12);
				updatePositions();
			}
		});	
		
		menu = createContextMenu();
	}
	
	private ContextMenu createContextMenu() {
		final ContextMenu menu = new ContextMenu(this.getCamera());
		
		for (final ViewRequirements viewReqs: NavigatorViewCreator.getViewRequirements().values()) {
			final String entryName = viewReqs.humanName() + " definieren";
			
			menu.add(entryName, new Runnable() {
				@Override
				public void run() {
					final Point2D point = getCamera().localToView(menu.getOffset());
					ViewBuilder viewBuilder = new ViewBuilder(DimensionBrowserCanvas.this, viewReqs);
					showViewBuilder(viewBuilder, (float) point.getX(),
												 (float) point.getY());
					viewBuilder.showRequirements();
				}
			});	
		}
		addInputEventListener(new PBasicInputEventHandler() {
			
			@Override
			public void mousePressed(PInputEvent event) {
				if (event.isRightMouseButton()) {
					ContextMenuOwner cmo = PNodes.getAncestorOfType(event.getPickedNode(), ContextMenuOwner.class);
					if (cmo != null) {
						cmo.getMenu().showAtView(event.getPosition(), event.getPickedNode());
						
					} else {
						menu.showAtView(event.getPosition(), event.getPickedNode());
						
					}
				}
				super.mousePressed(event);
			}
			
			@Override
			public void mouseReleased(PInputEvent event) {
				menu.remove();
				ContextMenuOwner cmo = PNodes.getAncestorOfType(event.getPickedNode(), ContextMenuOwner.class);
				if (cmo != null) {
					cmo.getMenu().remove();
				}
				super.mouseReleased(event);
			}
		});
		
		return menu;
	}
	
	public ContextMenu getMenu() {
		return menu;
	}
	
	public void addBlock (Block b) {
		getLayer().addChild(b);
		b.createBody(BodyType.DYNAMIC);
		this.blocks.add(b);
	}
	
	public void addConnection (Connection c) {
		getLayer().addChild(c);
		c.moveToBack();
		this.connections.add(c);
		c.updatePosition();
	}
	

	public void removeConnection(Connection c) {
		getLayer().removeChild(c);
		this.connections.remove(c);
	}
	
	
	public VisualDimension showDimension (Dimension dim, float x, float y) {
		if (shownDimensions.containsKey(dim)) {
			return shownDimensions.get(dim);
			
		} else {
			final VisualDimension visual = new VisualDimension(dim, this);
			visual.setGlobalTranslation(new Point2D.Double(x, y));
			addBlock(visual);
			shownDimensions.put(dim, visual);
			connectFamilyOf(dim);
			reconnectViewBuilders();
			return visual;
		}
	}
	
	private void reconnectViewBuilders() {
		for (ViewBuilder viewBuilder: Block.filter(blocks, ViewBuilder.class)) {
			viewBuilder.reconnectDimensions();
		}
	}

	public ViewBuilder showViewBuilder (ViewBuilder builder, float x, float y) {
		builder.setGlobalTranslation(new Point2D.Double(x, y));
		addBlock(builder);
		return builder;
	}
	
	private void connectFamilyOf (Dimension dim) {
		for (Dimension candidate: shownDimensions.keySet()) {
			if (candidate == dim) continue;
			
			if (candidate.isDirectChildOf(dim) || dim.isDirectChildOf(candidate)) {
				VisualDimension a = shownDimensions.get(dim);
				VisualDimension b = shownDimensions.get(candidate);
				if (!a.isConnectedTo(b) && !b.isConnectedTo(a)) {
					addConnection(Connections.fixedConnection(a, b));
				}
			}
		}
	}

	public void updatePositions () {
		for (Block b: blocks) {
			b.updatePosition();
		}
		
		for (Connection c: connections) {
			c.updatePosition();
		}
	}
	
	public World getWorld() {
		return world;
	}
	
	@Override
	public Context getContext() {
		return context;
	}

	public VisualDimension getVisualDimension(Dimension dim) {
		return this.shownDimensions.get(dim);
	}

	public Set<VisualDimension> getVisualDimensionOrParents(Dimension child) {
		if (shownDimensions.containsKey(child)) return Sets.newHashSet(getVisualDimension(child));
				
		Set<VisualDimension> vds = Sets.newHashSet();
		
		for (Dimension parent: child.parents()) {
			if (shownDimensions.containsKey(parent)) vds.add(shownDimensions.get(parent));
		}
		
		for (Dimension parent: child.parents()) {
			vds = Sets.union(vds, getVisualDimensionOrParents(parent));
		}
		
		return vds;
	}
}






